// StereoMaker
// Copyright(C) 2012 Vadim Trochinsky
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.


#include "stereo.h"
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>

#include <FreeImage.h>

void err_handler(FREE_IMAGE_FORMAT fif, const char *msg) {
	fprintf(stderr, "Error in JPEG library: %s\n", msg);
	exit(2);
}

FIBITMAP * load(char *file) {
	FIBITMAP *ret = NULL;

	fprintf(stderr, "Loading %s... ", file);
	ret = FreeImage_Load(FIF_JPEG, file, JPEG_ACCURATE  );

	if ( !ret ) {
		fprintf(stderr, "Can't load image %s\n", file);
		exit(1);
	}

	fprintf(stderr, "done.\n");
	return ret;
}

void copy_channel(FIBITMAP *source, FIBITMAP *dest, int channel) {
	FIBITMAP *tmp = NULL;

	
	if ( FreeImage_GetBPP(source) == 8 ) {
		// Greyscale image, can't copy a channel from those
		if (!FreeImage_SetChannel(dest, source, channel)) {
			fprintf(stderr, "Failed to set channel %i in 8 BPP image\n", channel);
		}
	} else {
		tmp = FreeImage_GetChannel(source, channel);
		if (!tmp) {
			fprintf(stderr, "Failed to copy channel %i\n", channel);
		} else {
			if (!FreeImage_SetChannel(dest, tmp, channel)) {
				fprintf(stderr, "Failed to set channel %i in image\n", channel);
			}
		
			FreeImage_Unload(tmp);
		}
	}

}


StereoGenerator* StereoAlloc() {
	StereoGenerator *ret = (StereoGenerator*)malloc(sizeof(*ret));

	if (!ret)
		return ret;

	ret->left_file  = NULL;
        ret->right_file = NULL;
	ret->combined_file = NULL;

        ret->img_left   = NULL;
        ret->img_right  = NULL;
        ret->img_result = NULL;

	ret->middle_cutoff = 0;

	return ret;
}

void StereoSetFile(StereoGenerator *stereo, int side, const char *file) {

	if ( side == STEREO_LEFT ) {
		stereo->left_file = strdup(file);
	} else if ( side == STEREO_RIGHT ) {
		stereo->right_file = strdup(file);
	} else if ( side == STEREO_COMBINED ) {
		stereo->combined_file = strdup(file);
	}
}

void StereoFree(StereoGenerator *stereo) {
	if ( stereo->img_left ) {
		FreeImage_Unload( stereo->img_left );
		stereo->img_left = NULL;
	}

	if ( stereo->img_right ) {
		FreeImage_Unload( stereo->img_right );
		stereo->img_right = NULL;
	}

	if ( stereo->img_result ) {
		FreeImage_Unload( stereo->img_result );
		stereo->img_result = NULL;
	}

}

void StereoSetMiddleCutoff(StereoGenerator *stereo, int num_pixels) {
	stereo->middle_cutoff = num_pixels;
}

int StereoCompute(StereoGenerator *stereo, int type) {
	FIBITMAP *temp_image = NULL;
	FIBITMAP *temp_left = NULL;
	FIBITMAP *temp_right = NULL;

	RGBQUAD pixel;
	BYTE    pixel_byte;

	int w = 0, h = 0;
	int row = 0, col = 0;
	int side = 0;

	StereoFree(stereo);

	if ( stereo->combined_file ) {
		temp_image = load(stereo->combined_file);
		w = (FreeImage_GetWidth(temp_image) / 2) - (stereo->middle_cutoff/2);
		h = FreeImage_GetHeight(temp_image);

		stereo->img_left  = FreeImage_Copy(temp_image, 0, 0, w, h);
		stereo->img_right = FreeImage_Copy(temp_image, FreeImage_GetWidth(temp_image) - w, 0, w, h);

		FreeImage_Unload( temp_image );
	} else {
		if ( !stereo->img_left && stereo->img_right ) {
			return STEREO_FILES_MISSING;
		}


		stereo->img_left = load(stereo->left_file);
		stereo->img_right = load(stereo->right_file);

		w = FreeImage_GetWidth(stereo->img_left);
		h = FreeImage_GetHeight(stereo->img_left);

		if ( FreeImage_GetWidth(stereo->img_right) != w || FreeImage_GetHeight(stereo->img_right) != h ) {
			StereoFree( stereo );
			return STEREO_SIZE_MISMATCH;
		}
	}


	fprintf(stderr, "Image size: %i x %i\n", w, h);

	fprintf(stderr, "Composing...\n");


	if ( type & STEREO_HORIZONTAL_INTERLACED ) {
		// So that the dest image has the same format
		stereo->img_result = FreeImage_Clone(stereo->img_left);

		for(row=0;row<h;row++) {
			side = (row % 2 == 0);

			if ( type & STEREO_FLIP )
				side = !side;

			FIBITMAP *source = (side == 0) ? stereo->img_left : stereo->img_right;

			FIBITMAP *tmp = FreeImage_Copy(source, 0, row, FreeImage_GetWidth(source), row+1);

			if ( !tmp ) {
				fprintf(stderr, "Failed to copy line %i\n", row);
				exit(1);
			}

			FreeImage_Paste(stereo->img_result, tmp, 0, row, 256);
		}
	} else if ( type & STEREO_VERTICAL_INTERLACED ) {
		// So that the dest image has the same format
		stereo->img_result = FreeImage_Clone(stereo->img_left);

		for(col=0;col<w;col++) {
			side = (col % 2 == 0);
			
			if ( type & STEREO_FLIP )
				side = !side;

			FIBITMAP *source = (side == 0) ? stereo->img_left : stereo->img_right;

			FIBITMAP *tmp = FreeImage_Copy(source, col, 0, col + 1,  FreeImage_GetHeight(source));

			if ( !tmp ) {
				fprintf(stderr, "Failed to copy column %i\n", col);
				exit(1);
			}

			FreeImage_Paste(stereo->img_result, tmp, col, 0, 256);
		}
	} else if ( type & STEREO_CHECKERBOARD ) {
		// So that the dest image has the same format
		stereo->img_result = FreeImage_Clone(stereo->img_left);

		for(row=0;row<h;row++) {
			for(col=0;col<w;col++) {

				side = ((col + (row%2)) % 2 == 0);

				if ( type & STEREO_FLIP )
					side = !side;

				FIBITMAP *source = side == 0 ? stereo->img_left : stereo->img_right;
				if (FreeImage_GetBPP(source) >= 16 ) {
					FreeImage_GetPixelColor(source, row, col, &pixel);
					FreeImage_SetPixelColor(stereo->img_result, row, col, &pixel);
				} else {
					FreeImage_GetPixelIndex(source, row, col, &pixel_byte);
					FreeImage_SetPixelIndex(stereo->img_result, row, col, &pixel_byte);
				}
			}
		}
	} else if ( type & STEREO_CROSS_EYED ) {
		stereo->img_result = FreeImage_Allocate(w * 2, h, 24, 0xFF000000, 0x00FF0000, 0x0000FF00);

		if ( type & STEREO_FLIP ) {
			FreeImage_Paste(stereo->img_result, stereo->img_left, 0, 0, 256);
			FreeImage_Paste(stereo->img_result, stereo->img_right, w, 0, 256);
		} else {
			FreeImage_Paste(stereo->img_result, stereo->img_right, 0, 0, 256);
			FreeImage_Paste(stereo->img_result, stereo->img_left, w, 0, 256);
		}
	} else if ( type & STEREO_ANAGLYPH ) {
		stereo->img_result = FreeImage_Allocate(w, h, 24, 0xFF000000, 0x00FF0000, 0x0000FF00);

		fprintf(stderr, "BPP: %i\n", FreeImage_GetBPP(stereo->img_left));

		if (!FreeImage_HasPixels(stereo->img_right)) {
			fprintf(stderr, "No pixels in right\n");
		}	

		if ( type & STEREO_FLIP ) {
			copy_channel(stereo->img_right, stereo->img_result, FICC_RED);
			copy_channel(stereo->img_left, stereo->img_result, FICC_GREEN);
			copy_channel(stereo->img_left, stereo->img_result, FICC_BLUE);
		} else {
			copy_channel(stereo->img_left, stereo->img_result, FICC_RED);
			copy_channel(stereo->img_right, stereo->img_result, FICC_GREEN);
			copy_channel(stereo->img_right, stereo->img_result, FICC_BLUE);
		}

	} else {
		fprintf(stderr, "Unknown type %i\n", type);
	}

	return STEREO_OK;
}

int StereoWriteOutput(StereoGenerator *stereo, const char *output, int format) {
	fprintf(stderr, "Writing to %s... ", output);

	int options = 0;

	switch(format) {
		case STEREO_FMT_JPEG:
			//options = JPEG_100;
			break;
		case STEREO_FMT_PNG:
			//options = PNG_Z_BEST_COMPRESSION;
			break;
	}

	if ( FreeImage_Save(format, stereo->img_result, output, options ) ) {
		fprintf(stderr, "done.\n");
		return STEREO_OK;
	} else {
		fprintf(stderr, "failed!\n");
		return STEREO_ERROR;
	}
}

void StereoDestroy(StereoGenerator *stereo) {
	StereoFree(stereo);

	if ( stereo->left_file ) {
		free(stereo->left_file);
		stereo->left_file = NULL;
	}

	if ( stereo->right_file ) {
		free(stereo->right_file);
		stereo->right_file = NULL;
	}

	if ( stereo->combined_file ) {
		free(stereo->combined_file);
		stereo->combined_file = NULL;
	}



	free(stereo);
}


